#include "OgreURLPrecompiledHeader.h"

#include "OgreURLSHA1.h"
#include "OgreException.h"

#include <memory.h>

namespace Ogre
{

//-------------------------------------------------------------------------------------------------------------------------------------------------
#define rol( x, y ) (((x) << (y)) | (((unsigned long)x) >> (32 - y)))
//-------------------------------------------------------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------------------------------------------------------
SHA1::SHA1()
: mBlockUsed(0)
, mLenHi(0)
, mLenLo(0)
, mFinalized(false)
{
	// initialise digests, see RFC 3174
	mDigest[0] = 0x67452301;
	mDigest[1] = 0xefcdab89;
	mDigest[2] = 0x98badcfe;
	mDigest[3] = 0x10325476;
	mDigest[4] = 0xc3d2e1f0;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
SHA1::~SHA1()
{
	// nothing to do
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void SHA1::update( const void* data, size_t size )
{
	// Is finalized?
	if( mFinalized )
	{
		OGRE_EXCEPT( Exception::ERR_INVALID_STATE,
			"Failed to update SHA1 object - SHA1 object alredy finalized.",
			"SHA1::update" );
	}

	unsigned char* bytes = (unsigned char*) data;
	unsigned long wordblock[16];

	// update the length field
	mLenLo += size;
	mLenHi += (mLenLo < size);

	if( mBlockUsed && mBlockUsed + size < 64 )
	{
		// trivial case, just add to block
		memcpy( mBlock64 + mBlockUsed, bytes, size );
		mBlockUsed += size;
	}
	else
	{
		while( mBlockUsed + size >= 64 )
		{
			memcpy( mBlock64 + mBlockUsed, bytes, 64 - mBlockUsed );
			bytes += 64 - mBlockUsed;
			size -= 64 - mBlockUsed;

			// now process the block, gather bytes big-endian into words
			for( unsigned short i = 0; i < 16; i++ )
			{
				wordblock[i] =
					(((unsigned long)mBlock64[i * 4]) << 24) |
					(((unsigned long)mBlock64[(i * 4) + 1]) << 16) |
					(((unsigned long)mBlock64[(i * 4) + 2]) << 8) |
					(unsigned long)mBlock64[i * 4 + 0];
			}

			transform( wordblock );
			mBlockUsed = 0;
		}

		memcpy( mBlock64, bytes, size );
		mBlockUsed = size;
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void SHA1::update( const String& data )
{
	update( (const void*)data.c_str(), data.size() );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void SHA1::transform( unsigned long* block )
{
	unsigned long a, b, c, d, e, w[80];

	for( unsigned short i = 0; i < 16; i++ )
	{
		w[i] = block[i];
	}

	for( unsigned short i = 16; i < 80; i++ )
	{
		unsigned long tmp = w[i - 3] ^ w[i -8] ^ w[i - 14] ^ w[i - 16];
		w[i] = rol( tmp, 1 );
	}

	a = mDigest[0];
	b = mDigest[1];
	c = mDigest[2];
	d = mDigest[3];
	e = mDigest[4];

	for( unsigned short i = 0; i < 20; i++ )
	{
		unsigned long tmp = rol( a, 5 ) + (( b & c ) | ( d & ~b )) + e + w[i] + 0x5a827999;
		e = d;
		d = c;
		c = rol( b, 30 );
		b = a;
		a = tmp;
	}

	for( unsigned short i = 20; i < 40; i++ )
	{
		unsigned long tmp = rol( a, 5 ) + ( b ^ c ^ d ) + e + w[i] + 0x6ed9eba1;
		e = d;
		d = c;
		c = rol( b, 30 );
		b = a;
		a = tmp;
	}

	for( unsigned short i = 40; i < 60; i++ )
	{
		unsigned long tmp = rol( a, 5 ) +  ( (b & c) | (b & d) | (c & d) ) + e + w[i] + 0x8f1bbcdc;
		e = d;
		d = c;
		c = rol( b, 30 );
		b = a;
		a = tmp;
	}

	for( unsigned short i = 60; i < 80; i++ )
	{
		unsigned long tmp = rol( a, 5 ) +  ( b ^ c ^ d ) + e + w[i] + 0xca62c1d6;
		e = d;
		d = c;
		c = rol( b, 30 );
		b = a;
		a = tmp;
	}

	mDigest[0] += a;
	mDigest[1] += b;
	mDigest[2] += c;
	mDigest[3] += d;
	mDigest[4] += e;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void SHA1::finalize()
{
	// Is finalized?
	if( mFinalized )
	{
		OGRE_EXCEPT( Exception::ERR_INVALID_STATE,
			"Failed to finalize SHA1 object - SHA1 object alredy finalized.",
			"SHA1::finalize" );
	}

	assert( mBlockUsed <= 120 && "too much blocks are used!!!" );
	size_t pad = ( mBlockUsed >= 56 ? 120 /*56 + 64*/ : 56 ) - mBlockUsed;
	unsigned char c[64];
	unsigned long lenhi = (mLenHi << 3) | (mLenLo >> 29 /*32 - 3*/);
	unsigned long lenlo = mLenLo << 3;

	memset( c, 0, pad );
	c[0] = 0x80;

	update( &c, pad );
	
	c[0] = (lenhi >> 24) & 0xff;
	c[1] = (lenhi >> 16) & 0xff;
	c[2] = (lenhi >> 8) & 0xff;
	c[3] = lenhi & 0xff;
	c[4] = (lenlo >> 24) & 0xff;
	c[5] = (lenlo >> 16) & 0xff;
	c[6] = (lenlo >> 8) & 0xff;
	c[7] = lenlo & 0xff;

	update( &c, 8 );

	for( unsigned short i = 0; i < 5; i++ )
	{
		mHash[i * 4] = (mDigest[i] >> 24) & 0xff;
		mHash[(i * 4) + 1] = (mDigest[i] >> 16) & 0xff;
		mHash[(i * 4) + 2] = (mDigest[i] >> 8) & 0xff;
		mHash[(i * 4) + 3] = mDigest[i] & 0xff;
	}

	mFinalized = true;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
const String& SHA1::getHexDigest()
{
	if( mFinalized )
	{
		return mHexDigest;
	}
	
	// filazile hash
	finalize();

	StringUtil::StrStreamType stream;
	stream << std::setfill( '0' ) << std::hex;
	for( unsigned short i = 0; i < 20; i++ )
	{
		stream << std::setw( 2 ) << (unsigned short)mHash[i];
	}

	mHexDigest = stream.str();
	return mHexDigest;
}

} // end of namespace Ogre
